//【SendWindowsMsg.js】代码

try{
	const ffi = require('ffi-napi');
	const ref = require('ref-napi');
	const Struct = require('ref-struct-di')(ref);
	const ULONG = ref.types.ulong;
	const POINTER = ref.refType(ref.types.void);
	const UINT = ref.types.uint;
	const ULONG_P = ref.refType(ULONG);
	const VOID_P = POINTER;

	// 重启系统im自动启动时路径有变，需要重置绝对路径，否则报Win32 error 126错误
	var oldPath = process.env.PATH;
	var dllPath = 'C:\\Windows\\SysWOW64\\';
	process.env['PATH'] = `${process.env.PATH};${dllPath}`;
	let User32 = new ffi.Library('user32.dll', {
	  // 'FindWindowW': [POINTER, [WCHAR_T_P, WCHAR_T_P]],
	  'SendMessageW': ["int32", ["int32", "uint", "uint64", "int64"]],
	});
	process.env['PATH'] = oldPath;
	  const User32SendMsg = function(hwnd, msg){
	      console.log("User32SendMsg开始 hwnd: " + hwnd + " | msg: " + msg);
	      var result = User32.SendMessageW(hwnd, msg, 0, 0);
	      console.log("User32SendMsg结束: " + result);
	      return result;
	  };

  module.exports = {User32SendMsg};
}catch(e){
  console.log("=======error: ffi-napi 引入 user32.dll 出错！！！")
  console.error(e);
}


===================================================================================

//【background.js】代码片段

以electron中使用为例：
// 翼丰信的窗口句柄WinId
let yfxIMWinId = 0;
// QT截图工具yfscreenshot的processId（实时获取，可能是手动启动）
let yfscreenshotProcessId = 0;
// QT截图工具yfscreenshot的句柄WinId（实时获取，可能是手动启动）
let yfscreenshotWinId = 0;
// QT截图工具yfscreenshot的processId（由IM启动）
let yfscreenshotProcessId_ImStart = 0;
// QT截图工具yfscreenshot的句柄WinId（由IM启动）
let yfscreenshotWinId_ImStart = 0;
// 是否需要重启截图（在IM启动中，截图工具退出的情况使用）
let isScreenshotRestartRequired = false;
// 发起截图请求窗口事件保存
// let ScreenShotRequestEvent;
const WM_USER_SCREENSHOT_START = 0x9010;
const WM_USER_SCREENSHOT_OK = 0x9020;
const WM_USER_SCREENSHOT_CANCEL = 0x9030;
/* = WM_COPYDATA */
const WM_USER_SCREENSHOT_WINID = 0x4A;
const yfscreenshotExeName = 'yfscreenshot.exe';

ipcMain.on("yfscreenshot-checkStatus", (event, args)=>{
    // 先确认截图工具是否已启动，如果已启动则杀掉，否则将无法把IM句柄更新至已启动的截图工具中
    checkIfScreenshotIsRunning();
});

function checkIfScreenshotIsRunning(){
    // 先确认截图工具是否已启动，如果已启动则杀掉，否则将无法把IM句柄更新至已启动的截图工具中
    getScreenshotPidWinId();
    if(yfscreenshotWinId != 0 || yfscreenshotProcessId != 0){
        console.log("**********************kill已启动的截图工具前: " + yfscreenshotProcessId + "|" + yfscreenshotWinId);
        // 在主进程启动时会check并kill一次，在login时再check+kill时可能processId还不为0，但winId已经为0，所以再次kill会报错，经判断无影响
        killYfScreenshotProcess();
        console.log("**********************kill已启动的截图工具后: " + yfscreenshotProcessId + "|" + yfscreenshotWinId);
    }
    yfscreenshotWinId = 0;
    yfscreenshotProcessId = 0;
    yfscreenshotWinId_ImStart = 0;
    yfscreenshotProcessId_ImStart = 0;
}


ipcMain.on("yfscreenshot-startup", (event, args)=>{
    // 启动截图工具yfscreenshot
    yfscreenshotWinId = 0;
    yfscreenshotProcessId = 0;
    yfscreenshotWinId_ImStart = 0;
    yfscreenshotProcessId_ImStart = 0;
    yfscreenshotStartup();
});

function yfscreenshotStartup(){
    // 启动QT截图工具yfscreenshot，启动后截图工具会发送WM_USER_SCREENSHOT_WINID消息
    var screenshortPath = getYfScreenshortAppPath();
    // 启动时需要携带当前electron应用的句柄
    screenshortPath += ' ' + yfxIMWinId.toString();
    yfscreenshotProcess = exec(screenshortPath);
}

//启动后，electron应用会hook到截图工具发送的WM_USER_SCREENSHOT_WINID消息
// 接收截图工具yfscreenshot消息
mainWindow.hookWindowMessage(WM_USER_SCREENSHOT_WINID, (wParam, lParam) => {
    console.log("接收到截图工具窗体id");
    getScreenshotPidWinId();

    // 如果属于截图工具重启的情况，需要再次启动截图
    if(isScreenshotRestartRequired){
        yfscreenshotBegin();
    }

    if (yfscreenshotProcess != null){
        var pid = yfscreenshotProcess.pid;
        exec("tskill " + pid, function (err){
            if (err){
                console.log("============ 启动yfscreenshot子线程kill ERROR:" + err);
            }else{
                console.log("yfscreenshotProcess(pid:" + pid +") Kill SUCCESS");
            }
        });
    }
});
    
function getScreenshotPidWinId(){
    try{
        var processId_winId = Buffer.alloc(32);
        processId_winId.type = ref.types.char;
        // 此处的GetScreenshotWinId为c++的dll，功能就是获取相应exe名称的进程id和窗口句柄，dll【ReadCopyDataMsg.dll】已在包中，使用方式在最后
        processId_winId = GetScreenshotWinId(yfscreenshotExeName);
        var str_actualWinId = ref.readCString(processId_winId, 0);
        console.log(str_actualWinId);
        yfscreenshotProcessId = str_actualWinId.split("|")[0];
        yfscreenshotWinId = str_actualWinId.split("|")[1];
        // 如果是首次由IM启动，则保存
        if(yfscreenshotProcessId_ImStart == 0){
            yfscreenshotProcessId_ImStart = yfscreenshotProcessId;
        }
        if(yfscreenshotWinId_ImStart == 0){
            yfscreenshotWinId_ImStart = yfscreenshotWinId;
        }
        console.log("getScreenshotPidWinId结果：" + yfscreenshotProcessId + "|" + yfscreenshotWinId);
    }catch(e){
        console.error('getScreenshotPidWinId error', e);
    }
}

function yfscreenshotBegin(){
    // 截图前先确认截图工具是否已启动
    getScreenshotPidWinId();
    if(yfscreenshotWinId != 0 && yfscreenshotProcessId != 0){
        // if(yfscreenshotProcessId == yfscreenshotProcessId_ImStart && yfscreenshotWinId == yfscreenshotWinId_ImStart){
        // 当有钉在桌面的截图时，yfscreenshotProcessId相同，但获取的yfscreenshotWinId可能不同（会获取到钉在桌面的截图的窗口），所以此处不能对yfscreenshotWinId判断，只要保证yfscreenshotProcessId相同就可以
        if(yfscreenshotProcessId == yfscreenshotProcessId_ImStart){
            // 则确定此为由当前IM启动的工具
            isScreenshotRestartRequired = false;
            // 截图工具启动中则直接执行截图
            // var sendResult = require("./SendWindowsMsg").User32SendMsg(yfscreenshotWinId, WM_USER_SCREENSHOT_START);
            // 当有钉在桌面的截图时，需要使用最初启动时的yfscreenshotWinId_ImStart
            var sendResult = require("./SendWindowsMsg").User32SendMsg(yfscreenshotWinId_ImStart, WM_USER_SCREENSHOT_START);
            console.log("**********************接执行截图sendResult: " + sendResult);
        }else{
            // 可能是手动kill掉IM启动的工具后，手动启动了工具，需要kill掉再重启截图工具
            console.log("**********************kill已启动的截图工具前2: " + yfscreenshotProcessId + "|" + yfscreenshotWinId);
            // 在主进程启动时会check并kill一次，在login时再check+kill时可能processId还不为0，但winId已经为0，所以再次kill会报错，经判断无影响
            killYfScreenshotProcess();
            yfscreenshotWinId = 0;
            yfscreenshotProcessId = 0;
            yfscreenshotWinId_ImStart = 0;
            yfscreenshotProcessId_ImStart = 0;
            console.log("**********************kill已启动的截图工具后2: " + yfscreenshotProcessId + "|" + yfscreenshotWinId);
            isScreenshotRestartRequired = true;
            yfscreenshotStartup();
        }
    }else{
        // 可能手动kill掉了工具，需要重启
        yfscreenshotWinId = 0;
        yfscreenshotProcessId = 0;
        yfscreenshotWinId_ImStart = 0;
        yfscreenshotProcessId_ImStart = 0;
        isScreenshotRestartRequired = true;
        yfscreenshotStartup();
    }
}

function killYfScreenshotProcess(){
    // 杀掉QT截图工具yfscreenshot进程
    console.log("============ QT截图工具yfscreenshot进程kill开始:" + yfscreenshotProcessId);
    exec("tskill " + yfscreenshotProcessId, function (err){
        if (err){
            console.log("============ QT截图工具yfscreenshot进程kill ERROR:" + err);
        }else{
            console.log("yfscreenshotProcessId(" + yfscreenshotProcessId +") Kill SUCCESS");
        }
    });
};

//点击electron中相关按钮（剪刀）开始截图：
ipcMain.on('screenshots-start', (event, args) => {
    clipboard.clear('clipboard');
    // 防止置顶时被微信抢夺光标
    let senderWinId = event.sender.id;
    let windows = BrowserWindow.getAllWindows();
    let sendWin = windows.filter(win => win.webContents.id === senderWinId);
    yfscreenshotBegin();
    // 后续截图后操作转到mainWindow.hookWindowMessage(WM_USER_SCREENSHOT_OK）事件中
    // 接收截图成功的消息
    mainWindow.hookWindowMessage(WM_USER_SCREENSHOT_OK, (wParam, lParam) => {
        console.log("get WM_USER_SCREENSHOT_OK message: 截图成功 ！");
        let pngs = clipboard.readImage().toPNG();
        let imgData = Buffer.from(pngs,'base64');
        console.log(imgData.length);
        if (sendWin.length > 0) {
            if(!sendWin[0].isVisible()){
                sendWin[0].show();
            }
            // 保证messageinput中getcurrentwindow能成功取到窗口
            sendWin[0].focus();
        }
        if(imgData.length > 0){
            let filename = tmp.tmpNameSync() + '.png';
            fs.writeFileSync(filename, pngs);
            // clipboard.clear('clipboard');
            // mainWindow.webContents.send('screenshots-ok', { filePath: filename });
            sendMessageFromSenderWindow(event, 'screenshots-ok', { filePath: filename });
        }else{
            if(sendWin.length > 0){
                // 激活截图窗口，否则压下的截图按钮一直是压下状态，需要手动拖动窗体后才能解消
                sendMessageFromSenderWindow(event, 'screenshots-win-activate', { });
            }
        }
        mainWindow.unhookWindowMessage(WM_USER_SCREENSHOT_OK);
        mainWindow.unhookWindowMessage(WM_USER_SCREENSHOT_CANCEL);
    });
    // 接收截图取消的消息
    mainWindow.hookWindowMessage(WM_USER_SCREENSHOT_CANCEL, (wParam, lParam) => {
        console.log("get WM_USER_SCREENSHOT_CANCEL message: 截图取消 ！");
        if (sendWin.length > 0) {
            if(!sendWin[0].isVisible()){
                sendWin[0].show();
            }
            // 保证messageinput中getcurrentwindow能成功取到窗口
            sendWin[0].focus();
            // 激活截图窗口，否则压下的截图按钮一直是压下状态，需要手动拖动窗体后才能解消
            sendWin[0].webContents.send('screenshots-win-activate', {});
        }
        mainWindow.unhookWindowMessage(WM_USER_SCREENSHOT_OK);
        mainWindow.unhookWindowMessage(WM_USER_SCREENSHOT_CANCEL);
    });
});


===================================================================================

//【ReadCopyDataMsg.dll】的使用方法：
//【ReadCopyDataMsg.js】代码（这个名字起得有点嘚，懒得改了）
// 不能用import，会报错，必须放在最外层引入
const { app } = require('electron');
try{
	const ffi = require('ffi-napi');
	var path = require('path');
	const ref = require('ref-napi');
	const Struct = require('ref-struct-di')(ref);
	const ULONG = ref.types.ulong;
	const POINTER = ref.refType(ref.types.void);
	const UINT = ref.types.uint;

	// 重启系统im自动启动时路径有变，需要重置绝对路径，否则报Win32 error 126错误（此处貌似不需要）
	const kernel32_ffi = ffi.Library('kernel32', {
	    SetDllDirectoryA: ['bool', ['string']]
	});
	// process.env['PATH'] = oldPath;
	// 重启系统im自动启动时路径有变，需要指定绝对路径，否则报Win32 error 126错误
	var resourcesPath = getResourcesPath();
	kernel32_ffi.SetDllDirectoryA(resourcesPath); // 必须指定绝对路径
	const COPYDATAPtr = ref.refType(COPYDATA2);
	// c++的dll中不能带namespace和class，否则找不到function
	const ReadCopyDataMsg = ffi.Library('ReadCopyDataMsg.dll', {
	      'GetScreenshotWinId': // 声明这个dll中的一个函数
	      [
	        ref.refType(ref.types.char), ['string'],  // 前为出力参数，后为入力参数
	      ],
	  });

	const GetScreenshotWinId = function(exeName){
	  console.log("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++GetScreenshotWinId开始: ");
	  var winId = ReadCopyDataMsg.GetScreenshotWinId(exeName);
	  console.log("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++GetScreenshotWinId结束: " + winId);
	  return winId;
	};

	module.exports = {GetScreenshotWinId};
}catch(e){
  console.log("=======error: ffi-napi 引入 ReadCopyDataMsg.dll 出错！！！")
  console.error(e);
}

// 此方法必须放在外层，而且不能写成const getResourcesPath = function(){}的形式，否则报错
function getResourcesPath(){
  if (process.env.WEBPACK_DEV_SERVER_URL){
    const devUrl =  'resources/dll/';
    return devUrl;
  }else{
      var appPath = path.dirname(app.getPath('exe'));
      const eveUrl = appPath + '\\resources\\dll\\';
      console.log("************ReadCopyDataMsg prod dllPath: " + eveUrl);
      return eveUrl;
  }
}

// 在background.js中引入即可
const { GetScreenshotWinId } = require("./ReadCopyDataMsg");
